// Fill out your copyright notice in the Description page of Project Settings.


#include "SnakeBase.h"

#include "Interactable.h"
#include "SnakeElementBase.h"

// Sets default values
ASnakeBase::ASnakeBase()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void ASnakeBase::BeginPlay()
{
	Super::BeginPlay();

	SetActorTickInterval(MovementSpeed);

	OnDestroyed.AddDynamic(this, &ASnakeBase::HandleSnakeDestroyed);
}

// Called every frame
void ASnakeBase::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (IsRuned)
	{
		Move();
		
		CanSetMovementDirection = true;
	}
}

void ASnakeBase::CreateSnake()
{
	FVector Position = {0, 0, 50};

	for (int i = 0; i < 3; i++)
	{
		if (SnakeElements.Num() > 0)
		{
			Position = SnakeElements.Last()->GetActorLocation();
			Position.X += MovementStep;
		}

		AddSnakeElement(Position);
	}
}

void ASnakeBase::Run()
{
	IsRuned = true;
}

void ASnakeBase::AddSnakeElement(FVector Position)
{
	const auto SnakeElement = GetWorld()->SpawnActor<ASnakeElementBase>(SnakeElementClass, FTransform(Position));
	SnakeElement->SnakeOwner = this;

	const int32 ElementIndex = SnakeElements.Add(SnakeElement);

	if (ElementIndex == 0)
	{
		SnakeElement->SetIsHead();
	}
}

void ASnakeBase::SetSnakeElementAddRequired()
{
	IsRequiredAddNewElement = true;
}

void ASnakeBase::SetSnakeTeleportRequired()
{
	IsRequiredTeleport = true;
}

void ASnakeBase::Move()
{
	FVector MovementVector {0, 0, 0};
	
	switch (CurrentMovementDirection)
	{
		case EMovementDirection::UP:
			MovementVector.X += MovementStep;
			break;
		case EMovementDirection::DOWN:
			MovementVector.X -= MovementStep;
			break;
		case EMovementDirection::LEFT:
			MovementVector.Y -= MovementStep;
			break;
		case EMovementDirection::RIGHT:
			MovementVector.Y += MovementStep;
			break;
	}

	// Move head
	auto PreviousElementPosition = SnakeElements[0]->GetActorLocation();
	SnakeElements[0]->AddActorWorldOffset(MovementVector, true);

	if (IsRequiredTeleport)
	{
		SnakeElements[0]->OffCollision();
		
		FVector TeleportPosition = SnakeElements[0]->GetActorLocation();
		
		switch (CurrentMovementDirection)
		{
		case EMovementDirection::UP:
		case EMovementDirection::DOWN:
			TeleportPosition.X *= -1;
			break;
		case EMovementDirection::LEFT:
		case EMovementDirection::RIGHT:
			TeleportPosition.Y *= -1;
			break;
		}

		SnakeElements[0]->SetActorLocation(FVector(TeleportPosition + MovementVector), true);

		IsRequiredTeleport = false;

		SnakeElements[0]->OnCollision();
	}
	
	// Move elements
	for (int i = 1; i < SnakeElements.Num(); i++)
	{
		const auto TempElementPosition = SnakeElements[i]->GetActorLocation();
		const bool IsTeleport = FVector::Distance(TempElementPosition, PreviousElementPosition) > MovementStep;

		if (IsTeleport)
		{
			SnakeElements[i]->OffCollision();
		}
	
		SnakeElements[i]->SetActorLocation(PreviousElementPosition, true);

		if (IsTeleport)
		{
			SnakeElements[i]->OnCollision();
		}

		PreviousElementPosition = TempElementPosition;
	}

	// Add new element
	if (IsRequiredAddNewElement)
	{
		AddSnakeElement(PreviousElementPosition);
		
		IsRequiredAddNewElement = false;
	}
}

EMovementDirection ASnakeBase::GetCurrentMovementDirection()
{
	return CurrentMovementDirection;
}

void ASnakeBase::SetMovementDirection(EMovementDirection MovementDirection)
{
	if (CanSetMovementDirection)
	{
		CurrentMovementDirection = MovementDirection;

		CanSetMovementDirection = false;
	}
}

void ASnakeBase::SnakeElementOverlap(ASnakeElementBase* OverlapElement, AActor* OtherActor)
{
	if (IsValid(OverlapElement))
	{
		IInteractable* InteractableInterface = Cast<IInteractable>(OtherActor);

		if (InteractableInterface)
		{
			InteractableInterface->Interact(this);
		}
	}
}

TArray<ASnakeElementBase*> ASnakeBase::GetElements()
{
	return this->SnakeElements;
}

void ASnakeBase::HandleSnakeDestroyed(AActor* DestroyedActor)
{
	OnSnakeDestroyedDelegate.Broadcast();
}
